/*    

 bobbietalk - a visual programming environment for toy robotics

 Copyright (C) 2013  Roberto Tiella

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.

 */
package bobbietalk.semantics.compiler;

import bobbietalk.gui.ProgrammingBlock;
import bobbietalk.gui.RobotConfiguration;
import bobbietalk.gui.blocks.EndBlock;
import bobbietalk.gui.blocks.ProgramLoader;
import bobbietalk.gui.blocks.parametrized.ParametrizedBlock;
import static bobbietalk.semantics.compiler.Compiler.bundle;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;

/**
 *
 * @author roberto
 */
public class NxcCompiler extends AbstractCompiler {

    private String nxcAbsolutePath;
    private CompilationContext program;
    private String firmwareVersion = "131";
    private ProgramLoader pl;

    public NxcCompiler(String nxcPath, String tmpDirPath, String firmwareVersion, ProgramLoader pl) {
        super(tmpDirPath);
        nxcAbsolutePath = nxcPath;
        program = new CompilationContext();
        this.firmwareVersion = firmwareVersion;
        this.pl = pl;
    }

    @Override
    protected boolean generateCode(BlockGraph g, StringBuilder sb, List<String> messages, boolean isSubroutine) {

        ProgrammingBlock b = g.getBeginBlock();

        if (!isSubroutine) {

            sb.append("task main() {").append('\n');

            generateRobotConfiguration(sb);

        }

        int vc = 0;

        while (!(b instanceof EndBlock)) {

            b = b.getNextBlock();

            if (!compileLegacyBlocks(b, sb)) {
                try {
                    if (!compileGenericBlocks(b, sb)) {

                        messages.add("block cannot be encoded: " + b);
                        return false;

                    }
                } catch (CompilerException ex) {
                    messages.add("block cannot be encoded: " + b);
                    messages.add("internal error;" + ex.getMessage());
                    return false;

                }
            }

        }

        if (!isSubroutine) {

            sb.append("}").append('\n');
        }

        return true;

    }

    protected boolean compileLegacyBlocks(ProgrammingBlock b, StringBuilder sb) {

        if (b instanceof EndBlock) {

           // Nothing to do

        } else {
            return false;
        }

        return true;
    }

    @Override
    protected boolean compileWithExternalTool(String ppath, List<String> messages) {
        try {

            // TODO -v depends on the firmware
            ProcessBuilder pb = new ProcessBuilder(nxcAbsolutePath, "-d", "-T=NXT", "-v=" + firmwareVersion, ppath);
            final File errorLog = new File(getTmpDir(), "nxc.error");
            pb.redirectError(errorLog);
            pb.redirectOutput(pb.redirectError());
            Process p = pb.start();

            int s = p.waitFor();

            if (s != 0) {
                messages.add(bundle.getString("compiler.nxc.exiterror"));

                BufferedReader br = new BufferedReader(new FileReader(errorLog));
                String line = null;
                while ((line = br.readLine()) != null) {
                    messages.add(line);
                }

                br.close();

                return false;
            }

            return true;

        } catch (IOException ex) {
            messages.add(bundle.getString("compiler.nxc.internalerror") + ex);
            return false;

        } catch (InterruptedException ex) {
            messages.add("can't compile program, reason:" + ex);
            return false;
        }



    }

    private boolean compileGenericBlocks(ProgrammingBlock b, StringBuilder sb) throws CompilerException {

        if (b instanceof ParametrizedBlock) {

            ParametrizedBlock pb = (ParametrizedBlock) b;

            String template = pb.getCodeTemplate("nxc");

            if (!template.startsWith("@META")) {


                VelocityContext context = new VelocityContext();

                context.put("block", pb);
                context.put("robotconf", getRobotConfiguration());
                context.put("program", program);

                StringWriter sw = new StringWriter();

                Velocity.evaluate(context, sw, "compile-" + b.getName(), template);

                sb.append(sw.toString());
                sb.append('\n');

                return true;

            } else {
                // handle @META

                String args[] = MetaCodeManager.getMetaArgs(template);

                String fileToInclude = null;

                if (args[0].equals("compile")) {
                    Reader freader = null;
                    try {
                        if (args.length != 2) {
                            throw new CompilerException("invalid data in meta code");
                        }

                        fileToInclude = pb.getAttributeValueAsString(args[1]);

                        File incSub = new File(fileToInclude);

                        freader = new BufferedReader(new FileReader(incSub));

                        List<ProgrammingBlock> blocks = new ArrayList<>();

                        NxcCompiler subC = new NxcCompiler(nxcAbsolutePath, getTmpDir().getAbsolutePath(), firmwareVersion, pl);
                        subC.setRobotConfiguration(getRobotConfiguration());


                        pl.sourceBlocks(freader, blocks);

                        ArrayList<String> messages = new ArrayList<>();

                        StringBuilder code = new StringBuilder();

                        if (!subC.compileOnly(blocks, messages, code, true)) {
                            throw new CompilerException("can't compile subroutine:" + fileToInclude);
                        }

                        sb.append(code.toString());

                    } catch (Exception ex) {
                        Logger.getLogger(NxcCompiler.class.getName()).log(Level.SEVERE, null, ex);
                        throw new CompilerException("can't compile subroutine:" + fileToInclude, ex);
                    } finally {
                        try {
                            freader.close();
                        } catch (IOException ex) {
                            throw new CompilerException("errors compiling subroutine:" + fileToInclude, ex);
                        }
                    }

                    return true;
                }
            }
        }

        return false;
    }

    @Override
    public String getExension() {

        return "nxc";

    }

    private void generateRobotConfiguration(StringBuilder sb) {

        RobotConfiguration rc = getRobotConfiguration();

        sb.append(String.format(
                "SetSensorTouch(IN_%1$s);\n"
                + "ResetSensor(IN_%1$s);\n", rc.getTouch1SensorPort()));

        sb.append(String.format(
                "SetSensorTouch(IN_%1$s);\n"
                + "ResetSensor(IN_%1$s);\n", rc.getTouch2SensorPort()));

        sb.append(String.format(
                "SetSensorColorRed(IN_%1$s);\n"
                + "ResetSensor(IN_%1$s);\n", rc.getColorSensorPort()));

        sb.append(String.format(
                "SetSensorUltrasonic(IN_%1$s);\n"
                + "ResetSensor(IN_%1$s);\n", rc.getDistanceSensorPort()));


    }
}
